/*
 * Copyright 2016-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onosproject.tetopology.management.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.onlab.util.KryoNamespace;
import org.onosproject.net.DeviceId;
import org.onosproject.store.AbstractStore;
import org.onosproject.store.serializers.KryoNamespaces;
import org.onosproject.store.service.AtomicCounter;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.MapEvent;
import org.onosproject.store.service.MapEventListener;
import org.onosproject.store.service.Serializer;
import org.onosproject.store.service.StorageService;
import org.onosproject.tetopology.management.api.CommonTopologyData;
import org.onosproject.tetopology.management.api.DefaultNetwork;
import org.onosproject.tetopology.management.api.DefaultTeTopologies;
import org.onosproject.tetopology.management.api.DefaultTeTopology;
import org.onosproject.tetopology.management.api.EncodingType;
import org.onosproject.tetopology.management.api.KeyId;
import org.onosproject.tetopology.management.api.LongValue;
import org.onosproject.tetopology.management.api.Network;
import org.onosproject.tetopology.management.api.OptimizationType;
import org.onosproject.tetopology.management.api.ProviderClientId;
import org.onosproject.tetopology.management.api.SwitchingType;
import org.onosproject.tetopology.management.api.TeConstants;
import org.onosproject.tetopology.management.api.TeStatus;
import org.onosproject.tetopology.management.api.TeTopologies;
import org.onosproject.tetopology.management.api.TeTopology;
import org.onosproject.tetopology.management.api.TeTopologyEvent;
import org.onosproject.tetopology.management.api.TeTopologyEvent.Type;
import org.onosproject.tetopology.management.api.TeTopologyId;
import org.onosproject.tetopology.management.api.TeTopologyKey;
import org.onosproject.tetopology.management.api.TeUtils;
import org.onosproject.tetopology.management.api.link.AsNumber;
import org.onosproject.tetopology.management.api.link.CommonLinkData;
import org.onosproject.tetopology.management.api.link.ConnectivityMatrixId;
import org.onosproject.tetopology.management.api.link.DefaultNetworkLink;
import org.onosproject.tetopology.management.api.link.DefaultTeLink;
import org.onosproject.tetopology.management.api.link.ElementType;
import org.onosproject.tetopology.management.api.link.ExternalLink;
import org.onosproject.tetopology.management.api.link.Label;
import org.onosproject.tetopology.management.api.link.LinkBandwidth;
import org.onosproject.tetopology.management.api.link.NetworkLink;
import org.onosproject.tetopology.management.api.link.NetworkLinkKey;
import org.onosproject.tetopology.management.api.link.OduResource;
import org.onosproject.tetopology.management.api.link.PathElement;
import org.onosproject.tetopology.management.api.link.TeIpv4;
import org.onosproject.tetopology.management.api.link.TeIpv6;
import org.onosproject.tetopology.management.api.link.TeLink;
import org.onosproject.tetopology.management.api.link.TeLinkId;
import org.onosproject.tetopology.management.api.link.TeLinkTpGlobalKey;
import org.onosproject.tetopology.management.api.link.TeLinkTpKey;
import org.onosproject.tetopology.management.api.link.TePathAttributes;
import org.onosproject.tetopology.management.api.link.TunnelProtectionType;
import org.onosproject.tetopology.management.api.link.UnderlayAbstractPath;
import org.onosproject.tetopology.management.api.link.UnderlayBackupPath;
import org.onosproject.tetopology.management.api.link.UnderlayPath;
import org.onosproject.tetopology.management.api.link.UnderlayPrimaryPath;
import org.onosproject.tetopology.management.api.link.UnnumberedLink;
import org.onosproject.tetopology.management.api.node.CommonNodeData;
import org.onosproject.tetopology.management.api.node.ConnectivityMatrix;
import org.onosproject.tetopology.management.api.node.ConnectivityMatrixKey;
import org.onosproject.tetopology.management.api.node.DefaultNetworkNode;
import org.onosproject.tetopology.management.api.node.DefaultTeNode;
import org.onosproject.tetopology.management.api.node.DefaultTerminationPoint;
import org.onosproject.tetopology.management.api.node.DefaultTunnelTerminationPoint;
import org.onosproject.tetopology.management.api.node.LocalLinkConnectivity;
import org.onosproject.tetopology.management.api.node.NetworkNode;
import org.onosproject.tetopology.management.api.node.NetworkNodeKey;
import org.onosproject.tetopology.management.api.node.NodeTpKey;
import org.onosproject.tetopology.management.api.node.TeNode;
import org.onosproject.tetopology.management.api.node.TeNodeKey;
import org.onosproject.tetopology.management.api.node.TerminationPoint;
import org.onosproject.tetopology.management.api.node.TerminationPointKey;
import org.onosproject.tetopology.management.api.node.TtpKey;
import org.onosproject.tetopology.management.api.node.TunnelTerminationPoint;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;

import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import static org.onosproject.tetopology.management.api.OptimizationType.NOT_OPTIMIZED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.LINK_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.LINK_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.LINK_UPDATED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NETWORK_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NETWORK_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NETWORK_UPDATED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NODE_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NODE_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.NODE_UPDATED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_LINK_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_LINK_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_LINK_UPDATED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_NODE_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_NODE_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_NODE_UPDATED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_TOPOLOGY_ADDED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_TOPOLOGY_REMOVED;
import static org.onosproject.tetopology.management.api.TeTopologyEvent.Type.TE_TOPOLOGY_UPDATED;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Implementation of the TE network store.
 */
@Component(immediate = true, service = TeTopologyStore.class)
public class DistributedTeTopologyStore
    extends AbstractStore<TeTopologyEvent, TeTopologyStoreDelegate>
    implements TeTopologyStore {
    private static final String STORE_NAME = "TE_NETWORK_TOPOLOGY_STORE";
    private static final String COUNTER_NAME = "TeTopology-TeTopologyId";
    private static final String TETOPOLOGYKEY_INTERNALTETOPOLOGY = "TeTopologyKey-InternalTeTopology";
    private static final String NETWORKID_NETWORK = "NetworkId-InternalNetwork";
    private static final String TENODEKEY_INTERNALTENODE = "TeNodeKey-InternalTeNode";
    private static final String CONNMATRIXKEY_CONNECTIVITYMATRIX = "ConnMatrixKey-ConnectivityMatrix";
    private static final String NETWORKNODEKEY_INTERNALNETWORKNODE = "NetworkNodeKey-InternalNetworkNode";
    private static final String TELINKGLOBALKEY_INTERNALTELINK = "TeLinkGlobalKey-InternalTeLink";
    private static final String NETWORKLINKKEY_INTERNALNETWORKLINK = "NetworkLinkKey-InternalNetworkLink";
    private static final String TPKEY_INTERNALTERMINATIONPOINT = "tpKey-InternalTerminationPoint";
    private static final String TELINKTPGLOBALKEY_TERMINATIONPOINTKEY = "TeLinkGlobalKey-TerminationPointKey";
    private static final String TTPKEY_TUNNELTERMINATIONPOINT = "TtpKey-TunnelTerminationPoint";
    private final Logger log = getLogger(getClass());

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected StorageService storageService;
    // Track TE topologies by TE Topology key
    private ConsistentMap<TeTopologyKey, InternalTeTopology> teTopologyConsistentMap;
    private Map<TeTopologyKey, InternalTeTopology> teTopologyMap;
    private AtomicCounter nextTeTopologyId;
     // Listener for te topology events
    private final MapEventListener<TeTopologyKey, InternalTeTopology> teTopologyListener =
            new InternalTeTopologyListener();
    // Track networks by network Id
    private ConsistentMap<KeyId, InternalNetwork> networkConsistentMap;
    private Map<KeyId, InternalNetwork> networkMap;
    // Listener for network events
    private final MapEventListener<KeyId, InternalNetwork> networkListener =
            new InternalNetworkListener();
    // Track TE nodes by TE node key
    private ConsistentMap<TeNodeKey, InternalTeNode> teNodeConsistentMap;
    private Map<TeNodeKey, InternalTeNode> teNodeMap;
    // Track ConnectivityMatrix by its key
    private ConsistentMap<ConnectivityMatrixKey, ConnectivityMatrix> connMatrixConsistentMap;
    private Map<ConnectivityMatrixKey, ConnectivityMatrix> connMatrixMap;
    // Track Tunnel Termination Points by its key
    private ConsistentMap<TtpKey, TunnelTerminationPoint> ttpConsistentMap;
    private Map<TtpKey, TunnelTerminationPoint> ttpMap;
    // Listener for TE node events
    private final MapEventListener<TeNodeKey, InternalTeNode> teNodeListener =
            new InternalTeNodeListener();
    // Track network nodes by network node key
    private ConsistentMap<NetworkNodeKey, InternalNetworkNode> networkNodeConsistentMap;
    private Map<NetworkNodeKey, InternalNetworkNode> networkNodeMap;
    // Listener for network node events
    private final MapEventListener<NetworkNodeKey, InternalNetworkNode> networkNodeListener =
            new InternalNetworkNodeListener();
    // Track TE links by its key
    private ConsistentMap<TeLinkTpGlobalKey, InternalTeLink> teLinkConsistentMap;
    private Map<TeLinkTpGlobalKey, InternalTeLink> teLinkMap;
    // Listener for te link events
    private final MapEventListener<TeLinkTpGlobalKey, InternalTeLink> teLinkListener =
        new InternalTeLinkListener();
    // Track network links by network link key
    private ConsistentMap<NetworkLinkKey, InternalNetworkLink> networkLinkConsistentMap;
    private Map<NetworkLinkKey, InternalNetworkLink> networkLinkMap;
    // Listener for network link events
    private final MapEventListener<NetworkLinkKey, InternalNetworkLink> networkLinkListener =
            new InternalNetworkLinkListener();
    // Track Termination points by termination point key
    private ConsistentMap<TerminationPointKey, InternalTerminationPoint> tpConsistentMap;
    private Map<TerminationPointKey, InternalTerminationPoint> tpMap;
    // Track termination point keys by TE termination point Key
    private ConsistentMap<TeLinkTpGlobalKey, TerminationPointKey> tpKeyConsistentMap;
    private Map<TeLinkTpGlobalKey, TerminationPointKey> tpKeyMap;
    private final BlockingQueue<TeTopologyMapEvent> mapEventQueue = new LinkedBlockingQueue<>();

    private long providerId;
    private static final Serializer TETOPOLOGY_SERIALIZER = Serializer
            .using(new KryoNamespace.Builder().register(KryoNamespaces.API)
                    .register(TeTopologyKey.class)
                    .register(ProviderClientId.class)
                    .register(TeNodeKey.class)
                    .register(TeLinkTpGlobalKey.class)
                    .register(CommonTopologyData.class)
                    .register(KeyId.class)
                    .register(OptimizationType.class)
                    .register(InternalTeTopology.class)
                    .register(InternalNetwork.class)
                    .register(InternalTerminationPoint.class)
                    .register(InternalTeNode.class)
                    .register(InternalNetworkNode.class)
                    .register(CommonNodeData.class)
                    .register(ConnectivityMatrixKey.class)
                    .register(ConnectivityMatrix.class)
                    .register(TtpKey.class)
                    .register(NetworkNodeKey.class)
                    .register(TeStatus.class)
                    .register(ElementType.class)
                    .register(TeIpv4.class)
                    .register(TeIpv6.class)
                    .register(AsNumber.class)
                    .register(Label.class)
                    .register(UnnumberedLink.class)
                    .register(TeLinkId.class)
                    .register(ConnectivityMatrixId.class)
                    .register(InternalTeLink.class)
                    .register(InternalNetworkLink.class)
                    .register(TeLinkTpKey.class)
                    .register(NetworkLinkKey.class)
                    .register(NodeTpKey.class)
                    .register(CommonLinkData.class)
                    .register(SwitchingType.class)
                    .register(EncodingType.class)
                    .register(ExternalLink.class)
                    .register(UnderlayPath.class)
                    .register(LinkBandwidth.class)
                    .register(OduResource.class)
                    .register(PathElement.class)
                    .register(UnderlayAbstractPath.class)
                    .register(UnderlayBackupPath.class)
                    .register(UnderlayPrimaryPath.class)
                    .register(TePathAttributes.class)
                    .register(TerminationPoint.class)
                    .register(TunnelTerminationPoint.class)
                    .register(DefaultTunnelTerminationPoint.class)
                    .register(TerminationPointKey.class)
                    .register(TunnelProtectionType.class)
                    .register(LongValue.class)
                    .register(LocalLinkConnectivity.class)
                    .build());

    /**
     * Distributed network store service activate method.
     */
    @Activate
    public void activate() {
        teTopologyConsistentMap = storageService
                .<TeTopologyKey, InternalTeTopology>consistentMapBuilder()
                .withSerializer(TETOPOLOGY_SERIALIZER)
                .withName(TETOPOLOGYKEY_INTERNALTETOPOLOGY)
                .withRelaxedReadConsistency()
                .build();
        teTopologyConsistentMap.addListener(teTopologyListener);
        teTopologyMap = teTopologyConsistentMap.asJavaMap();
        networkConsistentMap = storageService
                .<KeyId, InternalNetwork>consistentMapBuilder()
                .withSerializer(TETOPOLOGY_SERIALIZER)
                .withName(NETWORKID_NETWORK)
                .withRelaxedReadConsistency()
                .build();
        networkConsistentMap.addListener(networkListener);
        networkMap = networkConsistentMap.asJavaMap();
        teNodeConsistentMap = storageService
                .<TeNodeKey, InternalTeNode>consistentMapBuilder()
                .withSerializer(TETOPOLOGY_SERIALIZER)
                .withName(TENODEKEY_INTERNALTENODE)
                .withRelaxedReadConsistency()
                .build();
        teNodeConsistentMap.addListener(teNodeListener);
        teNodeMap = teNodeConsistentMap.asJavaMap();
        connMatrixConsistentMap = storageService
                 .<ConnectivityMatrixKey, ConnectivityMatrix>consistentMapBuilder()
                 .withSerializer(TETOPOLOGY_SERIALIZER)
                 .withName(CONNMATRIXKEY_CONNECTIVITYMATRIX)
                 .withRelaxedReadConsistency()
                 .build();
        connMatrixMap = connMatrixConsistentMap.asJavaMap();
        networkNodeConsistentMap = storageService
                 .<NetworkNodeKey, InternalNetworkNode>consistentMapBuilder()
                 .withSerializer(TETOPOLOGY_SERIALIZER)
                 .withName(NETWORKNODEKEY_INTERNALNETWORKNODE)
                 .withRelaxedReadConsistency()
                 .build();
        networkNodeConsistentMap.addListener(networkNodeListener);
        networkNodeMap = networkNodeConsistentMap.asJavaMap();
        teLinkConsistentMap = storageService
                 .<TeLinkTpGlobalKey, InternalTeLink>consistentMapBuilder()
                 .withSerializer(TETOPOLOGY_SERIALIZER)
                 .withName(TELINKGLOBALKEY_INTERNALTELINK)
                 .withRelaxedReadConsistency()
                 .build();
        teLinkConsistentMap.addListener(teLinkListener);
        teLinkMap = teLinkConsistentMap.asJavaMap();
        networkLinkConsistentMap = storageService
                 .<NetworkLinkKey, InternalNetworkLink>consistentMapBuilder()
                 .withSerializer(TETOPOLOGY_SERIALIZER)
                 .withName(NETWORKLINKKEY_INTERNALNETWORKLINK)
                 .withRelaxedReadConsistency()
                 .build();
        networkLinkConsistentMap.addListener(networkLinkListener);
        networkLinkMap = networkLinkConsistentMap.asJavaMap();
        tpConsistentMap = storageService
                 .<TerminationPointKey, InternalTerminationPoint>consistentMapBuilder()
                 .withSerializer(TETOPOLOGY_SERIALIZER)
                 .withName(TPKEY_INTERNALTERMINATIONPOINT)
                 .withRelaxedReadConsistency()
                 .build();
        tpMap = tpConsistentMap.asJavaMap();
        tpKeyConsistentMap = storageService
                  .<TeLinkTpGlobalKey, TerminationPointKey>consistentMapBuilder()
                  .withSerializer(TETOPOLOGY_SERIALIZER)
                  .withName(TELINKTPGLOBALKEY_TERMINATIONPOINTKEY)
                  .withRelaxedReadConsistency()
                  .build();
        tpKeyMap = tpKeyConsistentMap.asJavaMap();
        ttpConsistentMap = storageService
                  .<TtpKey, TunnelTerminationPoint>consistentMapBuilder()
                  .withSerializer(TETOPOLOGY_SERIALIZER)
                  .withName(TTPKEY_TUNNELTERMINATIONPOINT)
                  .withRelaxedReadConsistency()
                  .build();
        ttpMap = ttpConsistentMap.asJavaMap();

        nextTeTopologyId = storageService.getAtomicCounter(COUNTER_NAME);
        log.info("Started");
    }

    /**
     * Distributed network store service deactivate method.
     */
    @Deactivate
    public void deactivate() {
        teTopologyConsistentMap.removeListener(teTopologyListener);
        teTopologyConsistentMap.destroy();
        teTopologyMap.clear();
        networkConsistentMap.removeListener(networkListener);
        networkConsistentMap.destroy();
        networkMap.clear();
        teNodeConsistentMap.removeListener(teNodeListener);
        teNodeConsistentMap.destroy();
        teNodeMap.clear();
        connMatrixConsistentMap.destroy();
        connMatrixMap.clear();
        networkNodeConsistentMap.destroy();
        networkNodeConsistentMap.removeListener(networkNodeListener);
        networkNodeMap.clear();
        teLinkConsistentMap.removeListener(teLinkListener);
        teLinkConsistentMap.destroy();
        teLinkMap.clear();
        networkLinkConsistentMap.destroy();
        networkLinkConsistentMap.removeListener(networkLinkListener);
        networkLinkMap.clear();
        tpConsistentMap.destroy();
        tpMap.clear();
        tpKeyConsistentMap.destroy();
        tpKeyMap.clear();
        ttpConsistentMap.destroy();
        ttpMap.clear();
        mapEventQueue.clear();
        log.info("Stopped");
    }

    /**
     * Listener class to map listener map events to the TETOPOLOGY events.
     */
    private class InternalTeTopologyListener implements MapEventListener<TeTopologyKey, InternalTeTopology> {
        @Override
        public void event(MapEvent<TeTopologyKey, InternalTeTopology> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                type = TE_TOPOLOGY_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().childUpdate()) {
                    // Masked by the child events (e.g. Removal)
                    break;
                }
                type = TE_TOPOLOGY_UPDATED;
                break;
            case REMOVE:
                type = TE_TOPOLOGY_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setTeTopologyKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Listener class to map listener map events to the network events.
     */
    private class InternalNetworkListener implements MapEventListener<KeyId, InternalNetwork> {
        @Override
        public void event(MapEvent<KeyId, InternalNetwork> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                type = NETWORK_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().childUpdate()) {
                    // Masked by the child events (e.g. Removal)
                    break;
                }
                type = NETWORK_UPDATED;
                break;
            case REMOVE:
                type = NETWORK_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setNetworkKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Listener class to map listener map events to the TENODE events.
     */
    private class InternalTeNodeListener implements MapEventListener<TeNodeKey, InternalTeNode> {
        @Override
        public void event(MapEvent<TeNodeKey, InternalTeNode> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by the parent event (e.g. Add)
                    break;
                }
                type = TE_NODE_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().childUpdate() ||
                        event.newValue().value().parentUpdate()) {
                    // Masked by the child event (e.g. Removal) or parent event
                    break;
                }
                type = TE_NODE_UPDATED;
                break;
            case REMOVE:
                type = TE_NODE_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setTeNodeKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Listener class to map listener map events to the NETWORK NODE events.
     */
    private class InternalNetworkNodeListener implements MapEventListener<NetworkNodeKey, InternalNetworkNode> {
        @Override
        public void event(MapEvent<NetworkNodeKey, InternalNetworkNode> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by the parent event (e.g. Add)
                    break;
                }
                type = NODE_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().childUpdate() ||
                        event.newValue().value().parentUpdate()) {
                    // Masked by the child event (e.g. Removal) or parent event
                    break;
                }
                type = NODE_UPDATED;
                break;
            case REMOVE:
                type = NODE_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setNetworkNodeKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Listener class to map listener map events to the TELINK events.
     */
    private class InternalTeLinkListener implements MapEventListener<TeLinkTpGlobalKey, InternalTeLink> {
        @Override
        public void event(MapEvent<TeLinkTpGlobalKey, InternalTeLink> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by the parent event (e.g. Add)
                    break;
                }
                type = TE_LINK_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by parent event
                    break;
                }
                type = TE_LINK_UPDATED;
                break;
            case REMOVE:
                type = TE_LINK_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setTeLinkKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    /**
     * Listener class to map listener map events to the NETWORK LINK events.
     */
    private class InternalNetworkLinkListener implements MapEventListener<NetworkLinkKey, InternalNetworkLink> {
        @Override
        public void event(MapEvent<NetworkLinkKey, InternalNetworkLink> event) {
            Type type = null;
            switch (event.type()) {
            case INSERT:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by the parent event (e.g. Add)
                    break;
                }
                type = LINK_ADDED;
                break;
            case UPDATE:
                if (event.newValue().value().parentUpdate()) {
                    // Masked by the child event (e.g. Removal) or parent event
                    break;
                }
                type = LINK_UPDATED;
                break;
            case REMOVE:
                type = LINK_REMOVED;
                break;
            default:
                log.error("Unsupported event type: {}", event.type());
            }
            if (type != null) {
                TeTopologyMapEvent mapEvent = new TeTopologyMapEvent(type);
                mapEvent.setNetworkLinkKey(event.key());
                try {
                    mapEventQueue.put(mapEvent);
                } catch (InterruptedException e) {
                    log.warn("Unable to queue event {} ", mapEvent, e);
                    Thread.currentThread().interrupt();
                }
            }
        }
    }

    @Override
    public TeTopologies teTopologies() {
        Map<TeTopologyKey, TeTopology> teTopologies = Maps.newHashMap();
        if (MapUtils.isNotEmpty(teTopologyMap)) {
            for (TeTopologyKey key  : teTopologyMap.keySet()) {
                teTopologies.put(key, teTopology(key));
            }
        }
        return new DefaultTeTopologies(STORE_NAME, teTopologies);
    }

    private TeTopology teTopology(TeTopologyKey topologyId,
                                  InternalTeTopology intTopology) {
        if (intTopology == null) {
            return null;
        }
        Map<Long, TeNode> teNodes = null;
        if (CollectionUtils.isNotEmpty(intTopology.teNodeKeys())) {
            teNodes = Maps.newHashMap();
            for (TeNodeKey key : intTopology.teNodeKeys()) {
                teNodes.put(key.teNodeId(), teNode(key));
            }
        }
        Map<TeLinkTpKey, TeLink> teLinks = null;
        if (CollectionUtils.isNotEmpty(intTopology.teLinkKeys())) {
            teLinks = Maps.newHashMap();
            for (TeLinkTpGlobalKey key : intTopology.teLinkKeys()) {
                teLinks.put(key.teLinkTpKey(), teLink(key));
            }
        }
        return new DefaultTeTopology(topologyId, teNodes, teLinks,
                intTopology.teTopologyId(), intTopology.topologyData());
    }

    @Override
    public TeTopology teTopology(TeTopologyKey topologyId) {
        InternalTeTopology intTopology = teTopologyMap.get(topologyId);
        return teTopology(topologyId, intTopology);
    }

    private void removeTopologyeMapEntrys(InternalTeTopology curTopology) {
        // Remove TE nodes
        if (CollectionUtils.isNotEmpty(curTopology.teNodeKeys())) {
            for (TeNodeKey key : curTopology.teNodeKeys()) {
                removeTeNode(key, true);
            }
        }
        // Remove TE Links
        if (CollectionUtils.isNotEmpty(curTopology.teLinkKeys())) {
            for (TeLinkTpGlobalKey key : curTopology.teLinkKeys()) {
                removeTeLink(key, true);
            }
        }
    }

    @Override
    public void updateTeTopology(TeTopology teTopology) {
        InternalTeTopology curTopology = teTopologyMap.get(teTopology.teTopologyId());
        // Update TE nodes
        List<NetworkNodeKey> nodeIds = null;
        if (MapUtils.isNotEmpty(teTopology.teNodes())) {
            nodeIds = Lists.newArrayList();
            for (Map.Entry<Long, TeNode> entry : teTopology.teNodes().entrySet()) {
                TeNodeKey teNodeKey = new TeNodeKey(teTopology.teTopologyId(), entry.getKey());
                NetworkNodeKey nodeKey = TeMgrUtil.networkNodeKey(teNodeKey);
                updateTeNode(teNodeKey, entry.getValue(), true, true, nodeKey);
                nodeIds.add(nodeKey);
            }
        }
        // Update TE links
        List<NetworkLinkKey> linkIds = null;
        if (MapUtils.isNotEmpty(teTopology.teLinks())) {
            linkIds = Lists.newArrayList();
            for (Map.Entry<TeLinkTpKey, TeLink> entry : teTopology.teLinks().entrySet()) {
                TeLinkTpGlobalKey teLinkKey = new TeLinkTpGlobalKey(teTopology.teTopologyId(),
                        entry.getKey());
                NetworkLinkKey linkKey = TeMgrUtil.networkLinkKey(teLinkKey);
                updateTeLink(teLinkKey, entry.getValue(), true, true, linkKey);
                linkIds.add(linkKey);
            }
        }
        // Finally Update teTopologyMap
        InternalTeTopology newTopology = new InternalTeTopology(teTopology);
        teTopologyMap.put(teTopology.teTopologyId(), newTopology);

        if (curTopology == null) {
            // New topology, update networkMap
            InternalNetwork intNetwork = new InternalNetwork();
            intNetwork.setServerProvided(false);
            intNetwork.setTeTopologyKey(teTopology.teTopologyId());
            intNetwork.setNodeIds(nodeIds);
            intNetwork.setLinkIds(linkIds);
            networkMap.put(teTopology.networkId(), intNetwork);
        }
    }

    @Override
    public void removeTeTopology(TeTopologyKey topologyId) {
        // Remove it from teTopologyMap
        InternalTeTopology topology = teTopologyMap.remove(topologyId);
        if (topology != null) {
            removeTopologyeMapEntrys(topology);
            // Remove it from networkMap;
            networkMap.remove(topology.topologyData().networkId());
        }
    }

    @Override
    public List<Network> networks() {
        if (MapUtils.isEmpty(networkMap)) {
            return null;
        }
        List<Network> networks = Lists.newArrayList();
        for (KeyId networkId : networkMap.keySet()) {
            networks.add(network(networkId));
        }
        return networks;
    }

    private Network network(KeyId networkId, InternalNetwork curNetwork) {
        if (curNetwork == null) {
            return null;
        }
        List<KeyId> supportingNetworkIds = curNetwork.supportingNetworkIds();
        Map<KeyId, NetworkNode> nodes = null;
        if (CollectionUtils.isNotEmpty(curNetwork.nodeIds())) {
            nodes = Maps.newHashMap();
            for (NetworkNodeKey key : curNetwork.nodeIds()) {
                nodes.put(key.nodeId(), networkNode(key));
            }
        }
        Map<KeyId, NetworkLink> links = null;
        if (CollectionUtils.isNotEmpty(curNetwork.linkIds())) {
            links = Maps.newHashMap();
            for (NetworkLinkKey key : curNetwork.linkIds()) {
                links.put(key.linkId(), networkLink(key));
            }
        }
        TeTopologyId topologyId = null;
        DeviceId ownerId = null;
        OptimizationType opt = NOT_OPTIMIZED;
        if (curNetwork.teTopologyKey() != null &&
                teTopologyMap.get(curNetwork.teTopologyKey()) != null) {
            topologyId = new TeTopologyId(curNetwork.teTopologyKey().providerId(),
                                          curNetwork.teTopologyKey().clientId(),
                                          teTopologyMap.get(curNetwork.teTopologyKey())
                                                  .teTopologyId());
            ownerId = teTopologyMap.get(curNetwork.teTopologyKey())
                    .topologyData().ownerId();
            opt = teTopologyMap.get(curNetwork.teTopologyKey()).topologyData().optimization();
        }
        return new DefaultNetwork(networkId, supportingNetworkIds, nodes, links,
                                  topologyId, curNetwork.serverProvided(), ownerId, opt);
    }

    @Override
    public Network network(KeyId networkId) {
        InternalNetwork curNetwork = networkMap.get(networkId);
        return network(networkId, curNetwork);
    }

    private void removeNetworkMapEntrys(InternalNetwork curNetwork, boolean teRemove) {
        // Remove TE nodes
        if (CollectionUtils.isNotEmpty(curNetwork.nodeIds())) {
            for (NetworkNodeKey key : curNetwork.nodeIds()) {
                removeNetworkNode(key, teRemove);
            }
        }
        // Remove TE Links
        if (CollectionUtils.isNotEmpty(curNetwork.linkIds())) {
            for (NetworkLinkKey key : curNetwork.linkIds()) {
                removeNetworkLink(key, teRemove);
            }
        }
    }

    private TeTopologyKey newTeTopologyKey(TeTopologyId teTopologyId) {
        long idValue;
        try {
            idValue = Long.parseLong(teTopologyId.topologyId());
        } catch (NumberFormatException e) {
            // Can't get the long value from the string.
            // Use an assigned id value from local id pool,
            // Ideally id should be assigned per provider base.
            idValue = nextTeTopologyId();
        }
        return new TeTopologyKey(teTopologyId.providerId(), teTopologyId.clientId(), idValue);
    }

    @Override
    public void updateNetwork(Network network) {
        log.debug("updateNetwork {}", network);
        InternalNetwork curNetwork = networkMap.get(network.networkId());
        TeTopologyKey topoKey = null;
        if (network.teTopologyId() != null) {
            topoKey = newTeTopologyKey(network.teTopologyId());
        }
        // Update TE nodes
        List<TeNodeKey> teNodeKeys = null;
        if (MapUtils.isNotEmpty(network.nodes())) {
            teNodeKeys = Lists.newArrayList();
            for (Map.Entry<KeyId, NetworkNode> entry : network.nodes().entrySet()) {
                NetworkNodeKey nodeKey = new NetworkNodeKey(network.networkId(), entry.getKey());
                TeNodeKey teNodeKey = null;
                if (topoKey != null && entry.getValue().teNode() != null) {
                    teNodeKey = new TeNodeKey(topoKey, entry.getValue().teNode().teNodeId());
                }
                updateNetworkNode(nodeKey, entry.getValue(), true, false, teNodeKey);
                teNodeKeys.add(teNodeKey);
            }
        }
        // Update TE links
        List<TeLinkTpGlobalKey> teLinkKeys = null;
        if (MapUtils.isNotEmpty(network.links())) {
            teLinkKeys = Lists.newArrayList();
            for (Map.Entry<KeyId, NetworkLink> entry : network.links().entrySet()) {
                NetworkLinkKey linkKey = new NetworkLinkKey(network.networkId(), entry.getKey());
                TeLinkTpGlobalKey teLinkKey = null;
                if (topoKey != null && entry.getValue().teLink() != null) {
                    teLinkKey = new TeLinkTpGlobalKey(topoKey, entry.getValue().teLink().teLinkKey());
                }
                updateNetworkLink(linkKey, entry.getValue(), true, false, teLinkKey);
                teLinkKeys.add(teLinkKey);
            }
        }

        // New network, update TE Topology first
        if (curNetwork == null) {
            InternalTeTopology intTopo = new InternalTeTopology(network.teTopologyId().topologyId());
            intTopo.setTeNodeKeys(teNodeKeys);
            intTopo.setTeLinkKeys(teLinkKeys);
            BitSet flags = new BitSet(TeConstants.FLAG_MAX_BITS);
            flags.set(TeTopology.BIT_LEARNT);
            if (network.teTopologyId().clientId() == providerId) {
                // Hard rule for now
                flags.set(TeTopology.BIT_CUSTOMIZED);
            }
            CommonTopologyData common = new CommonTopologyData(network.networkId(),
                                                               network.optimization(),
                                                               flags,
                                                               network.ownerId());
            intTopo.setTopologydata(common);
            teTopologyMap.put(topoKey, intTopo);
        }
        // Finally Update networkMap
        InternalNetwork newNetwork = new InternalNetwork(network);
        newNetwork.setTeTopologyKey(topoKey);
        networkMap.put(network.networkId(), newNetwork);
    }

    @Override
    public void removeNetwork(KeyId networkId) {
        // Remove it from networkMap
        InternalNetwork network = networkMap.remove(networkId);
        if (network != null && network.teTopologyKey() != null) {
            removeNetworkMapEntrys(network, false);
            teTopologyMap.remove(network.teTopologyKey());
        }
    }

    private TeNode teNode(TeNodeKey nodeKey, InternalTeNode intNode) {
        if (intNode == null) {
            return null;
        }
        Map<Long, ConnectivityMatrix> connMatrices = null;
        if (CollectionUtils.isNotEmpty(intNode.connMatrixKeys())) {
            connMatrices = Maps.newHashMap();
            for (ConnectivityMatrixKey key : intNode.connMatrixKeys()) {
                connMatrices.put(key.entryId(), connMatrixMap.get(key));
            }
        }
        List<Long> teLinkIds = null;
        if (CollectionUtils.isNotEmpty(intNode.teLinkTpKeys())) {
            teLinkIds = Lists.newArrayList();
            for (TeLinkTpGlobalKey key : intNode.teLinkTpKeys()) {
                teLinkIds = TeUtils.addListElement(teLinkIds, key.teLinkTpId());
            }
        }
        List<Long> tps = null;
        if (CollectionUtils.isNotEmpty(intNode.teTpKeys())) {
            tps = Lists.newArrayList();
            for (TeLinkTpGlobalKey key : intNode.teTpKeys()) {
                tps = TeUtils.addListElement(tps, key.teLinkTpId());
            }
        }
        Map<Long, TunnelTerminationPoint> ttps = null;
        if (CollectionUtils.isNotEmpty(intNode.ttpKeys())) {
            ttps = Maps.newHashMap();
            for (TtpKey key : intNode.ttpKeys()) {
                ttps.put(key.ttpId(), ttpMap.get(key));
            }
        }
        return new DefaultTeNode(nodeKey.teNodeId(),
                intNode.underlayTopologyKey(),
                intNode.supportNodeKey(),
                intNode.sourceTeNodeKey(),
                intNode.teData(),
                connMatrices, teLinkIds, ttps, tps);
    }

    @Override
    public TeNode teNode(TeNodeKey nodeKey) {
        InternalTeNode intNode = teNodeMap.get(nodeKey);
        return teNode(nodeKey, intNode);
    }

    private void removeTeNodeMapEntrys(InternalTeNode intNode) {
        // Remove connMatrixMap entries for the node
        if (CollectionUtils.isNotEmpty(intNode.connMatrixKeys())) {
            for (ConnectivityMatrixKey key : intNode.connMatrixKeys()) {
                connMatrixMap.remove(key);
            }
        }
        // Remove ttpMap entries for the node
        if (CollectionUtils.isNotEmpty(intNode.ttpKeys())) {
            for (TtpKey key : intNode.ttpKeys()) {
                ttpMap.remove(key);
            }
        }
    }

    private void updateTeNode(TeNodeKey nodeKey, TeNode node, boolean parentUpdate,
            boolean teNodeUpdate, NetworkNodeKey networkNodeKey) {
        InternalTeTopology intTopo = teTopologyMap.get(nodeKey.teTopologyKey());
        if (intTopo == null && !parentUpdate) {
            log.error("TE Topology is not in dataStore for nodeUpdate {}", nodeKey);
            return;
        }
        InternalTeNode curNode = teNodeMap.get(nodeKey);
        // Update connMatrixMap
        if (MapUtils.isNotEmpty(node.connectivityMatrices())) {
            for (Map.Entry<Long, ConnectivityMatrix> entry : node.connectivityMatrices().entrySet()) {
                connMatrixMap.put(new ConnectivityMatrixKey(nodeKey, entry.getKey()),
                        entry.getValue());
            }
        }
        // Update ttpMap
        if (MapUtils.isNotEmpty(node.tunnelTerminationPoints())) {
            for (Map.Entry<Long, TunnelTerminationPoint> entry : node.tunnelTerminationPoints().entrySet()) {
                ttpMap.put(new TtpKey(nodeKey, entry.getKey()), entry.getValue());
            }
        }
        // Update teNodeMap
        InternalTeNode intNode = new InternalTeNode(nodeKey, node, networkNodeKey, parentUpdate);
        teNodeMap.put(nodeKey, intNode);
        if (curNode == null && !parentUpdate && intTopo != null) {
            // Update InternalTeTopology
           intTopo.setChildUpdate(true);
           TeUtils.addListElement(intTopo.teNodeKeys(), nodeKey);
        }
        // Update networkNodeMap
        if (teNodeUpdate) {
            updateNetworkNode(networkNodeKey, networkNode(node), parentUpdate,
                    teNodeUpdate, nodeKey);
        }
    }

    private NetworkNode networkNode(TeNode node) {
        KeyId nodeId = KeyId.keyId(Long.toString(node.teNodeId()));
        List<NetworkNodeKey> supportingNodeIds = null;
        if (node.supportingTeNodeId() != null) {
            supportingNodeIds = Lists.newArrayList();
            supportingNodeIds.add(new NetworkNodeKey(
                    TeMgrUtil.toNetworkId((node.supportingTeNodeId().teTopologyKey())),
                    KeyId.keyId(Long.toString(node.supportingTeNodeId().teNodeId()))));
        }
        Map<KeyId, TerminationPoint> tps = null;
        if (node.teTerminationPointIds() != null) {
            tps = Maps.newHashMap();
            for (Long teTpId : node.teTerminationPointIds()) {
                tps.put(KeyId.keyId(Long.toString(teTpId)),
                        new DefaultTerminationPoint(KeyId.keyId(Long.toString(teTpId)),
                                null, teTpId));
            }
        }
        return new DefaultNetworkNode(nodeId, supportingNodeIds, node, tps);
    }

    @Override
    public void updateTeNode(TeNodeKey nodeKey, TeNode node) {
        updateTeNode(nodeKey, node, false, true, TeMgrUtil.networkNodeKey(nodeKey));
    }

    private void removeTeNode(TeNodeKey nodeKey, boolean teNodeRemove) {
        // Remove it from InternalTeTopology first
        InternalTeTopology intTopo = teTopologyMap.get(nodeKey.teTopologyKey());
        if (intTopo != null && CollectionUtils.isNotEmpty(intTopo.teNodeKeys())) {
            intTopo.setChildUpdate(true);
            intTopo.teNodeKeys().remove(nodeKey);
        }
        // Then remove it from teNodeMap
        InternalTeNode node = teNodeMap.remove(nodeKey);

        if (node == null) {
            log.error("No node found for nodeKey {}", nodeKey);
            return;
        }

        removeTeNodeMapEntrys(node);
        // Remove it from networkNodeMap
        if (teNodeRemove && node != null) {
            removeNetworkNode(node.networkNodeKey(), teNodeRemove);
        }
    }

    @Override
    public void removeTeNode(TeNodeKey nodeKey) {
        removeTeNode(nodeKey, true);
    }

    private NetworkNode networkNode(NetworkNodeKey nodeKey, InternalNetworkNode intNode) {
        if (intNode == null) {
            return null;
        }
        Map<KeyId, TerminationPoint> tps = Maps.newHashMap();
        for (KeyId tpId : intNode.tpIds()) {
            tps.put(tpId, terminationPoint(
                    new TerminationPointKey(nodeKey, tpId)));

        }
        return new DefaultNetworkNode(nodeKey.nodeId(), intNode.supportingNodeIds(),
                teNode(intNode.teNodeKey()), tps);
    }

    @Override
    public NetworkNode networkNode(NetworkNodeKey nodeKey) {
        InternalNetworkNode intNode = networkNodeMap.get(nodeKey);
        return networkNode(nodeKey, intNode);
    }

    private void updateNetworkNode(NetworkNodeKey nodeKey, NetworkNode node,
            boolean parentUpdate, boolean teNodeUpdate, TeNodeKey teNodeKey) {
        InternalNetwork intNework = null;
        if (!parentUpdate) {
            intNework = networkMap.get(nodeKey.networkId());
            if (intNework == null) {
                log.error("Network is not in dataStore for nodeUpdate {}", nodeKey);
                return;
            }
        }

        InternalNetworkNode exNode = networkNodeMap.get(nodeKey);
        if (exNode != null && CollectionUtils.isNotEmpty(exNode.tpIds())) {
            // Remove the TerminationPoints first
            for (KeyId tpId : exNode.tpIds()) {
                removeTerminationPoint(new TerminationPointKey(nodeKey, tpId));
            }
        }

        if (MapUtils.isNotEmpty(node.terminationPoints())) {
            // Update with new TerminationPoints
            for (Map.Entry<KeyId, TerminationPoint> entry : node.terminationPoints().entrySet()) {
                updateTerminationPoint(new TerminationPointKey(nodeKey, entry.getKey()),
                        entry.getValue(), parentUpdate, teNodeKey);
            }
        }

        // Update teNodeMap first
        if (!teNodeUpdate && teNodeKey != null && node.teNode() != null) {
            updateTeNode(teNodeKey, node.teNode(), parentUpdate, teNodeUpdate, nodeKey);
        }
        // Update networkNodeMap
        InternalNetworkNode intNode = new InternalNetworkNode(node, parentUpdate);
        intNode.setTeNodeKey(teNodeKey);
        networkNodeMap.put(nodeKey, intNode);
        if (exNode == null && !parentUpdate && intNework != null) {
            // Update the InternalNetwork
            intNework.setChildUpdate(true);
            TeUtils.addListElement(intNework.nodeIds(), nodeKey);
        }
    }

    @Override
    public void updateNetworkNode(NetworkNodeKey nodeKey, NetworkNode node) {
        TeNodeKey teNodeKey = null;
        if (node.teNode() != null) {
            teNodeKey = new TeNodeKey(networkMap.get(nodeKey.networkId()).teTopologyKey(),
                                      node.teNode().teNodeId());
        }
        updateNetworkNode(nodeKey, node, false, false, teNodeKey);
    }

    private void removeNetworkNode(NetworkNodeKey nodeKey, boolean teNodeRemove) {
        // Update the InternalNetwork
        InternalNetwork intNework = networkMap.get(nodeKey.networkId());
        if (intNework != null && CollectionUtils.isNotEmpty(intNework.nodeIds())) {
            intNework.setChildUpdate(true);
            intNework.nodeIds().remove(nodeKey);
        }
        InternalNetworkNode intNode = networkNodeMap.remove(nodeKey);
        if (intNode != null && CollectionUtils.isNotEmpty(intNode.tpIds())) {
            // Remove the TerminationPoints first
            for (KeyId tpId : intNode.tpIds()) {
                removeTerminationPoint(new TerminationPointKey(nodeKey, tpId));
            }
        }
        if (!teNodeRemove && intNode != null) {
            // Now remove it from teNodeMap
            removeTeNode(intNode.teNodeKey(), teNodeRemove);
        }
    }

    @Override
    public void removeNetworkNode(NetworkNodeKey nodeKey) {
        removeNetworkNode(nodeKey, false);
    }

    private TeLink teLink(TeLinkTpGlobalKey linkKey, InternalTeLink intLink) {
        if (intLink == null) {
            return null;
        }
        return new DefaultTeLink(linkKey.teLinkTpKey(),
                intLink.peerTeLinkKey(),
                intLink.underlayTopologyKey(),
                intLink.supportingLinkKey(),
                intLink.sourceTeLinkKey(),
                intLink.teData());
    }

    @Override
    public TeLink teLink(TeLinkTpGlobalKey linkKey) {
        InternalTeLink intLink = teLinkMap.get(linkKey);
        return teLink(linkKey, intLink);
    }

    private void updateTeLink(TeLinkTpGlobalKey linkKey, TeLink link,
            boolean parentUpdate, boolean teLinkUpdate, NetworkLinkKey networkLinkKey) {
        InternalTeTopology intTopo = teTopologyMap.get(linkKey.teTopologyKey());
        if (intTopo == null && !parentUpdate) {
            log.error("TE Topology is not in dataStore for linkUpdate {}", linkKey);
            return;
        }
        InternalTeNode intNode = teNodeMap.get(linkKey.teNodeKey());
        if (intNode == null && !parentUpdate) {
            log.error("TE node is not in dataStore for linkUpdate {}", linkKey);
            return;
        }
        InternalTeLink exLink = teLinkMap.get(linkKey);

        // Update teLinkMap
        InternalTeLink intLink = new InternalTeLink(link, parentUpdate);
        intLink.setNetworkLinkKey(networkLinkKey);
        teLinkMap.put(linkKey, intLink);
        if (exLink == null && !parentUpdate) {
            if (intTopo != null) {
                // Update the InternalTeTopology
                intTopo.setChildUpdate(true);
                intTopo.setTeLinkKeys(TeUtils.addListElement(intTopo.teLinkKeys(), linkKey));
            }
            if (intNode != null) {
                // Update the InternalNode
                intNode.setChildUpdate(true);
                intNode.setTeLinkTpKeys(TeUtils.addListElement(intNode.teLinkTpKeys(), linkKey));
            }
        }

        // Update networkLinkMap
        if (teLinkUpdate) {
            updateNetworkLink(networkLinkKey, networkLink(link), parentUpdate,
                    teLinkUpdate, linkKey);
        }
    }

    private NetworkLink networkLink(TeLink link) {
        KeyId linkId = TeMgrUtil.toNetworkLinkId(link.teLinkKey());
        NodeTpKey source = null;
        if (link.teLinkKey() != null) {
            source = new NodeTpKey(KeyId.keyId(Long.toString(link.teLinkKey().teNodeId())),
                                   KeyId.keyId(Long.toString(link.teLinkKey().teLinkTpId())));
        }
        NodeTpKey dest = null;
        if (link.peerTeLinkKey() != null) {
            dest = new NodeTpKey(KeyId.keyId(Long.toString(link.peerTeLinkKey().teNodeId())),
                    KeyId.keyId(Long.toString(link.peerTeLinkKey().teLinkTpId())));
        }
        List<NetworkLinkKey> supportingLinkIds = null;
        if (link.supportingTeLinkId() != null) {
            supportingLinkIds = Lists.newArrayList();
            supportingLinkIds.add(new NetworkLinkKey(
                    TeMgrUtil.toNetworkId(link.supportingTeLinkId().teTopologyKey()),
                    TeMgrUtil.toNetworkLinkId(link.supportingTeLinkId().teLinkTpKey())));
        }
        return new DefaultNetworkLink(linkId, source, dest, supportingLinkIds, link);
    }

    @Override
    public void updateTeLink(TeLinkTpGlobalKey linkKey, TeLink link) {
        updateTeLink(linkKey, link, false, true, TeMgrUtil.networkLinkKey(linkKey));
    }

    private void removeTeLink(TeLinkTpGlobalKey linkKey, boolean teLinkRemove) {
        // Remove it from InternalTeTopology first
        InternalTeTopology intTopo = teTopologyMap.get(linkKey.teTopologyKey());
        if (intTopo != null && CollectionUtils.isNotEmpty(intTopo.teLinkKeys())) {
           intTopo.setChildUpdate(true);
           intTopo.teLinkKeys().remove(linkKey);
        }
        // Remove it from InternalTeNode
        InternalTeNode intNode = teNodeMap.get(linkKey.teNodeKey());
        if (intNode != null && CollectionUtils.isNotEmpty(intNode.teLinkTpKeys())) {
            intNode.setChildUpdate(true);
            intNode.teLinkTpKeys().remove(linkKey);
        }
        // Then remove it from teLinkMap
        InternalTeLink link = teLinkMap.remove(linkKey);
        if (teLinkRemove && link != null) {
            // Remove it from networkLinkMap
            removeNetworkLink(link.networkLinkKey(), teLinkRemove);
        }
     }

    @Override
    public void removeTeLink(TeLinkTpGlobalKey linkKey) {
        removeTeLink(linkKey, true);
    }

    private NetworkLink networkLink(NetworkLinkKey linkKey, InternalNetworkLink intLink) {
        if (intLink == null) {
            return null;
        }
        return new DefaultNetworkLink(linkKey.linkId(), intLink.source(),
                intLink.destination(), intLink.supportingLinkIds(), teLink(intLink.teLinkKey()));
    }

    @Override
    public NetworkLink networkLink(NetworkLinkKey linkKey) {
        InternalNetworkLink intLink = networkLinkMap.get(linkKey);
        return networkLink(linkKey, intLink);
    }

    private void updateNetworkLink(NetworkLinkKey linkKey, NetworkLink link,
            boolean parentUpdate, boolean teLinkUpdate, TeLinkTpGlobalKey teLinkKey) {
        InternalNetwork intNework = null;
        if (!parentUpdate) {
            intNework = networkMap.get(linkKey.networkId());
            if (intNework == null) {
                log.error("Network is not in dataStore for linkUpdate {}", linkKey);
                return;
            }
        }

        InternalNetworkLink exLink = networkLinkMap.get(linkKey);

        // Now update teLinkMap first
        if (!teLinkUpdate && teLinkKey != null) {
            updateTeLink(teLinkKey, link.teLink(), parentUpdate, teLinkUpdate, linkKey);
        }
        // Update networkLinkMap
        InternalNetworkLink intLink = new InternalNetworkLink(link, parentUpdate);
        intLink.setTeLinkKey(teLinkKey);
        networkLinkMap.put(linkKey, intLink);
        if (exLink == null && !parentUpdate && intNework != null) {
            // Update the InternalNetwork
            intNework.setChildUpdate(true);
            TeUtils.addListElement(intNework.linkIds(), linkKey);
        }
    }

    @Override
    public void updateNetworkLink(NetworkLinkKey linkKey, NetworkLink link) {
        TeLinkTpGlobalKey teLinkKey = null;
        if (link.teLink() != null) {
            teLinkKey = new TeLinkTpGlobalKey(networkMap.get(linkKey.networkId()).teTopologyKey(),
                                              link.teLink().teLinkKey());
        }

        updateNetworkLink(linkKey, link, false, false, teLinkKey);
    }

    private void removeNetworkLink(NetworkLinkKey linkKey, boolean teLinkRemove) {
        // Update the InternalNetwork
        InternalNetwork intNework = networkMap.get(linkKey.networkId());
        if (intNework != null && CollectionUtils.isNotEmpty(intNework.linkIds())) {
            intNework.setChildUpdate(true);
            intNework.linkIds().remove(linkKey);
        }
        // Remove it from networkLinkMap
        InternalNetworkLink intLink = networkLinkMap.remove(linkKey);
        if (!teLinkRemove && intLink != null && intLink.teLinkKey() != null) {
            // Now remove it from teLinkMap
            removeTeLink(intLink.teLinkKey(), teLinkRemove);
        }
    }

    @Override
    public void removeNetworkLink(NetworkLinkKey linkKey) {
        removeNetworkLink(linkKey, false);
    }

    private TerminationPoint terminationPoint(TerminationPointKey tpKey) {
        InternalTerminationPoint intTp = tpMap.get(tpKey);
        if (intTp == null) {
            return null;
        }
        return new DefaultTerminationPoint(tpKey.tpId(), intTp.supportingTpIds(),
                intTp.teTpKey().teLinkTpId());
    }

    private void updateTerminationPoint(TerminationPointKey tpKey,
            TerminationPoint tp, boolean parentUpdate, TeNodeKey teNodeKey) {
        TeNodeKey myTeNodeKey;
        InternalNetworkNode intNode = null;
        if (!parentUpdate) {
            intNode = networkNodeMap.get(tpKey);
            if (intNode == null) {
                log.error(" node is not in dataStore for tp update {}", tpKey);
                return;
            }
            myTeNodeKey = intNode.teNodeKey();
        } else {
            myTeNodeKey = teNodeKey;
        }
        TeLinkTpGlobalKey teTpKey = new TeLinkTpGlobalKey(myTeNodeKey, tp.teTpId());

        boolean newTp = tpMap.get(tpKey) == null;
        InternalTerminationPoint intTp = new InternalTerminationPoint(tp);
        intTp.setTeTpKey(teTpKey);
        tpMap.put(tpKey, intTp);
        if (newTp) {
            // Update tpKeyMap
            tpKeyMap.put(teTpKey, tpKey);
            if (!parentUpdate && intNode != null) {
                // Update InternalNetworkNode
                intNode.setChildUpdate(true);
                intNode.setTpIds(TeUtils.addListElement(intNode.tpIds(), tpKey.tpId()));
            }
        }
    }

    @Override
    public void updateTerminationPoint(TerminationPointKey tpKey,
            TerminationPoint tp) {
        updateTerminationPoint(tpKey, tp, false, null);
    }

    @Override
    public void removeTerminationPoint(TerminationPointKey tpKey) {
        // Update InternalNetworkNode
        InternalNetworkNode intNode = networkNodeMap.get(tpKey);
        if (intNode != null && CollectionUtils.isNotEmpty(intNode.tpIds())) {
            intNode.setChildUpdate(true);
            intNode.tpIds().remove(tpKey.tpId());
        }
        // Remove it from tpMap
        InternalTerminationPoint tp = tpMap.remove(tpKey);
        // Remove it from tpKeyMap
        if (tp != null) {
            tpKeyMap.remove(tp.teTpKey());
        }
    }

    @Override
    public TunnelTerminationPoint tunnelTerminationPoint(TtpKey ttpId) {
        return ttpMap.get(ttpId);
    }

    @Override
    public long nextTeTopologyId() {
        return nextTeTopologyId.getAndIncrement();
    }

    @Override
    public long nextTeNodeId(TeTopologyKey topoKey) {
        return teTopologyMap.get(topoKey).nextTeNodeId();
    }

    @Override
    public void setNextTeNodeId(TeTopologyKey topoKey, long nextNodeId) {
        teTopologyMap.get(topoKey).setNextTeNodeId(nextNodeId);
    }

    @Override
    public KeyId networkId(TeTopologyKey teTopologyKey) {
        return teTopologyMap.get(teTopologyKey) == null ||
               teTopologyMap.get(teTopologyKey).topologyData() == null ? null :
                    teTopologyMap.get(teTopologyKey).topologyData().networkId();
    }

    @Override
    public NetworkNodeKey nodeKey(TeNodeKey teNodeKey) {
        return teNodeMap.get(teNodeKey) == null ? null :
               teNodeMap.get(teNodeKey).networkNodeKey();
    }

    @Override
    public NetworkLinkKey linkKey(TeLinkTpGlobalKey teLinkKey) {
        return teLinkMap.get(teLinkKey) == null ? null :
               teLinkMap.get(teLinkKey).networkLinkKey();
    }

    @Override
    public TerminationPointKey terminationPointKey(TeLinkTpGlobalKey teTpKey) {
        return tpKeyMap.get(teTpKey);
    }

    @Override
    public BlockingQueue<TeTopologyMapEvent> mapEventQueue() {
        return mapEventQueue;
    }

    @Override
    public void setProviderId(long providerId) {
        this.providerId = providerId;
    }
}

